import React, { useEffect, useState, useMemo } from 'react'
import { Select, Divider, Pagination, Spin, message } from 'antd'
const { Option } = Select
import debounce from 'lodash.debounce'
import qs from 'qs'
import './index.less'
import { SearchOutlined } from '@ant-design/icons'
import PropTypes from 'prop-types'

const SearchSelect = ({
                        axios,
                        method = 'post',
                        url = '',
                        queryParams = {}, // 默认搜索参数
                        queryKeyName = 'queryKeyword', // 搜索关键字字段
                        pageSize = 10, // 搜索，每页条数键值
                        pageSizeKeyName = 'pageSize', // 搜索条件，每页条数字段
                        currentPageKeyName = 'curPage', // 搜索条件，当前页字段

                        labelKeyName = 'label', // option label 字段
                        valueKeyName = 'value', // option value 字段
                        disabledKeyName = 'disabled', // option disabled 字段

                        flagKeyName = 'flag', // 判断接口返回成功失败标识字段
                        flagValue = 1, // 判断接口返回成功失败标识键值
                        responseDataKeyName = 'data', // 响应数据res.data字段
                        responseCurrentPageName = currentPageKeyName, // 响应数据，当前页字段
                        totalRowsKeyName = 'totalRows', // 响应数据，总条数字段
                        listKeyName = 'dataList', // 响应数据，列表字段
                        mode, // 设置 Select 的模式为多选或标签	multiple | tags
                        allowClear = true, // 支持清除
                        startSearchIndex = 0, // 自定义接口搜索起始索引
                        qsStringify, // 是否使用qs模块序列化参数
                        selectStyle = {}, // select样式
                        extraSuffix = () => {}, // 额外的后缀label
                        value,
                        selectedOptionList = [], // 回显数据，需要配合form.setFieldsValue才能回显成功
                        onChange = () => {}, // onChange
                        max = 999, // 最多选择数量，只在 mode 为 multiple 或 tags 时有效
                        lang = 'zh', // 选择数量超过max值时的提示语国际化类型
                        renderOptions = null, // 自定义option
                        responseDataFormat = data => data, // 修改接口返回数据
                        requestFail = () => null,
                        ...otherProps
                      }) => {
  const [searchText, setSearchText] = useState('') // 搜索文本，防抖用
  const [list, setList] = useState([]) // 列表
  const [paginationData, setPaginationData] = useState({
    current: 1, // 当前页
    pageSize: Number(pageSize), // 分页大小
    total: 0 // 总条数
  })

  const [totalPage, setTotalPage] = useState(0)
  const [loading, setLoading] = useState(false)
  const difference = 1 - startSearchIndex // pagination.current - startSearchIndex，前端分页起始值与后端搜索起始值的差值
  const [selectValue, setSelectValue] = useState()
  const [isPaging, setIsPaging] = useState(true) // 接口是否分页
  const [isRenderSelected, setIsRenderSelected] = useState(false) // 是否已渲染回显
  const [defaultSelectedDataMap, setDefaultSelectedDataMap] = useState({}) // 与入参回显数据相匹配的接口数据

  // 根据value回显
  useEffect(() => {
    const defaultValue = value || otherProps.defaultValue

    if (defaultValue === undefined) return
    const fetchData = () => {
      const newVal = Array.isArray(defaultValue) ? defaultValue.map(d => d.toString()) : defaultValue.toString()
      const newValDataType = Object.prototype.toString.call(newVal)
      const selectValueDataType = Object.prototype.toString.call(selectValue)

      let flag = false
      if (newValDataType === '[object Array]' && selectValueDataType === '[object Array]') {
        const newValMap = {}
        for (const item of newVal) {
          newValMap[item] = item
        }
        for (const item of selectValue) {
          if (!newValMap[item]) {
            flag = true
            break
          }
        }
      } else {
        flag = newVal !== ((selectValue || selectValue === 0) ? selectValue.toString() : selectValue)
      }
      if (flag) {
        getList().then((dataList) => {
          if (typeof dataList[0]?.value === 'number') {
            setSelectValue(Number(newVal))
          } else {
            setSelectValue(newVal.toString())
          }
        })
      }
    }
    fetchData()
  }, [value, otherProps.defaultValue])

  // 合并回显数据
  const formatList = (arr = []) => {
    if (selectedOptionList.length && !isRenderSelected) {
      // 获取value 数据类型（number/string）
      let valueDataType = ''
      if (typeof arr[0]?.value === 'number') {
        valueDataType = 'number'
      } else {
        valueDataType = 'string'
      }
      // 回显数据转map
      const selectedOptionMap = new Map()
      // 与入参回显数据相匹配的接口数据
      let selectedOptionResMap = {}
      let i = 0 // 计数器
      for (const item of selectedOptionList) {
        if (valueDataType === 'number' && typeof item[valueKeyName] !== 'number') {
          item[valueKeyName] = Number(item[valueKeyName])
        } else if (valueDataType === 'string' && typeof item[valueKeyName] !== 'string') {
          item[valueKeyName] = item[valueKeyName].toString()
        }
        selectedOptionMap.set(item[valueKeyName], item)
      }
      for (const item of arr) {
        if (selectedOptionMap.get(item[valueKeyName])) {
          selectedOptionMap.delete(item[valueKeyName])
          // 相等，获取接口中的数据项
          selectedOptionResMap[item[valueKeyName]] = item
          i++ // 计数，当对比完回显数据时结束循环，节省循环次数
          if (i === selectedOptionList.length) {
            break
          }
        }
      }

      // 将一个键值对数组转为对象(Map转Object)
      const obj = Object.fromEntries(selectedOptionMap)
      // 获取未匹配到的剩余数据 list (Object 转 Array)
      const remainingList = Object.values(obj)
      // 将剩余数据与接口list合并做回显
      const newList = [...arr, ...remainingList]
      const remainingMap = {}
      for (const item of remainingList) {
        remainingMap[item[valueKeyName]] = item
      }
      selectedOptionResMap = { ...selectedOptionResMap, ...remainingMap }
      setDefaultSelectedDataMap(selectedOptionResMap)
      // 已回显，下次不再走上面的复杂逻辑
      setIsRenderSelected(true)
      setList(newList)
      // 渲染完成后，将合并的数据去除
      setTimeout(() => {
        setList(arr)
      })
    } else {
      setList(arr)
    }
  }

  // 获取数据
  const getList = async(value, paginationParams = {}) => {
    const timer = setTimeout(() => {
      setLoading(true)
    }, 300)
    // 整合入参
    const searchData = {
      ...queryParams,
      [currentPageKeyName]: (paginationParams.current - difference || paginationParams.current - difference === 0) ? paginationParams.current - difference : paginationData.current - difference,
      [pageSizeKeyName]: paginationParams.pageSize || paginationData.pageSize,
      [queryKeyName]: value
    }
    // 是否使用qs模块序列化入参
    const params = qsStringify ? qs.stringify(searchData) : searchData
    const resData = await axios[method](url, params).catch((error) => {
      setLoading(false)
      clearTimeout(timer)
      console.error(error)
    })
    // 用户自定义修改相应数据
    const formatData = responseDataFormat(resData)
    // 解构响应数据
    const { [flagKeyName]: flag, [responseDataKeyName]: data = {}, [responseDataKeyName]: { [listKeyName]: dataList = [], [totalRowsKeyName]: totalRows, [responseCurrentPageName]: curPage } = {}} = formatData
    if (flag === flagValue) {
      if (Object.prototype.toString.call(data) === '[object Array]') {
        // 不分页
        setIsPaging(false)
      }
      // 获取总页数
      setTotalPage(Math.ceil(totalRows / Number(pageSize)))
      // 设置新的分页数据
      setPaginationData({
        ...paginationData,
        current: curPage + difference,
        total: totalRows
      })
      formatList(dataList)

      return Promise.resolve(dataList)
    } else {
      requestFail && requestFail(resData)
    }
    setLoading(false)
    clearTimeout(timer)
  }

  // 搜索
  const handleSearch = debounce((value) => {
    setSearchText(value)
    getList(value)
  }, 300)

  // 选中后，处理多选、单选
  const handleChange = (value, selectedOptions) => {
    // 多选
    if (Array.isArray(value) && ['tags', 'multiple'].includes(mode)) {
      // 超过多大值，删除多余的
      if (value.length > max) {
        value.slice(0, max)
        selectedOptions.slice(0, max)
        let text = ''
        switch (lang) {
          case 'zh': // 中文
            text = `最多可选${max}个`
            break
          case 'en': // 英文
            text = `A maximum of ${max} can be selected`
            break
          case 'es': // 西班牙文
            text = `Hasta ${max} Opciones`
            break
          case 'ja': // 日文
            text = `${max}つまで選べる`
            break
          default:
            text = `最多可选${max}个`
            break
        }
        message.warning(text)
        return
      }

      const list = []
      selectedOptions.forEach((item, index) => {
        if (Object.keys(item).length === 0) {
          list[index] = defaultSelectedDataMap[value[index]]
        } else {
          list[index] = item.row
        }
      })
      setSelectValue(value)
      onChange(value, list)
    } else { // 单选、多选时不超过直接赋值获取
      setSelectValue(value)
      onChange(value, selectedOptions?.row)
    }
  }

  // perf: 获取焦点时，没有选择过则初始搜索，有选择值则使用当前分页和选项数据
  const handleFocus = () => {
    if (Array.isArray(selectValue) ? !selectValue.length : !selectValue) {
      setPaginationData({
        current: 1, // 当前页
        pageSize: Number(pageSize), // 分页大小
        total: paginationData.total // 总条数
      })
      getList()
    }
    otherProps.onFocus?.()
  }

  // 情况时，将分页、搜索条件都初始化
  const handleClear = () => {
    setPaginationData({
      current: 1, // 当前页
      pageSize: Number(pageSize), // 分页大小
      total: 0 // 总条数
    })
    setSearchText(undefined)
    if (otherProps.mode === 'multiple') {
      setSelectValue([])
    } else {
      setSelectValue(undefined)
    }
    // 此处因为 setPaginationData 异步导致current bug，因此传参防止异步
    getList(undefined, { current: 1 })
    otherProps.onClear?.()
  }

  // 下拉框
  const renderHtmlOptions = useMemo(() => {
    if (renderOptions) {
      return renderOptions(list)
    } else {
      return list.map((item, index) => {
        const extraLabel = extraSuffix(item)
        return <Option
          className='search-select__option_1Y2h3h66'
          disabled={item[disabledKeyName]}
          key={item[valueKeyName] || `${index}-${paginationData[currentPageKeyName]}`}
          value={item[valueKeyName]}
          label={item[labelKeyName]}
          row={item}
        >
          <div style={{ display: 'flex', justifyContent: 'space-between' }}>
            <span className='search-select__option-label' title={item[labelKeyName]}>{item[labelKeyName]}</span>
            {
              extraLabel &&
              <span className='search-select__option-extra-label' title={extraLabel}>{extraLabel}</span>
            }
          </div>
        </Option>
      })
    }
  }, [list, renderOptions])

  // 分页
  const dropdownRender = (menu) => {
    return (
      <>
        {totalPage > 1 ? (
          <div
            style={{
              display: 'flex',
              padding: '0 5px 5px',
              justifyContent: 'flex-end'
            }}
          >
            <Pagination
              simple
              size='small'
              total={paginationData.total}
              pageSize={paginationData.pageSize}
              current={paginationData.current}
              onChange={(current) => {
                getList(searchText, { current })
              }}
            />
          </div>
        ) : null}
        <Divider style={{ margin: 0 }} />
        {menu}
      </>
    )
  }

  // loading & 当下拉列表为空时显示的内容
  const notFoundContent = () => {
    return (
      loading ? <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}><Spin size='small' /></div>
        : <div role='listbox' id='rc_select_0_list' className='ant-select-item-empty'>
          <div className='ant-empty ant-empty-normal ant-empty-small'>
            <div className='ant-empty-image'>
              <svg className='ant-empty-img-simple' width='64' height='41' viewBox='0 0 64 41'
                   xmlns='http://www.w3.org/2000/svg'>
                <g transform='translate(0 1)' fill='none' fillRule='evenodd'>
                  <ellipse className='ant-empty-img-simple-ellipse' cx='32' cy='33' rx='32' ry='7' />
                  <g className='ant-empty-img-simple-g' fillRule='nonzero'>
                    <path
                      d='M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z' />
                    <path
                      d='M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z'
                      className='ant-empty-img-simple-path' />
                  </g>
                </g>
              </svg>
            </div>
            <div className='ant-empty-description'>暂无数据</div>
          </div>
        </div>
    )
  }

  return (
    <Select
      {...otherProps}
      showSearch
      loading
      suffixIcon={otherProps.suffixIcon || <SearchOutlined />}
      allowClear={allowClear}
      mode={mode}
      notFoundContent={notFoundContent()}
      getPopupContainer={(triggerNode) => triggerNode.parentElement}
      dropdownRender={dropdownRender}
      onSearch={handleSearch}
      onChange={handleChange}
      onFocus={handleFocus}
      onClear={handleClear}
      value={selectValue}
      filterOption={(input, option) => {
        if (isPaging) {
          if (Object.prototype.toString.call(option.children) === '[object Object]') {
            const str1 = option.children.props.children[0] ? option.children.props.children[0].props.children : ''
            const str2 = option.children.props.children[1] ? option.children.props.children[1].props.children : ''
            const str = str1 + str2
            return str.toLowerCase().indexOf(input.toLowerCase()) >= 0
          } else if (typeof option.children === 'string') {
            return option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
          } else {
            return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
          }
        } else {
          return false
        }
      }}
      style={{
        ...selectStyle
      }}
    >
      {renderHtmlOptions}
    </Select>
  )
}

// 类型检查
SearchSelect.propTypes = {
  axios: PropTypes.func.isRequired,
  method: PropTypes.string,
  url: PropTypes.string.isRequired,
  queryParams: PropTypes.object,
  queryKeyName: PropTypes.string,
  pageSize: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string
  ]),
  pageSizeKeyName: PropTypes.string,
  currentPageKeyName: PropTypes.string,
  labelKeyName: PropTypes.string,
  valueKeyName: PropTypes.string,
  disabledKeyName: PropTypes.string,
  flagKeyName: PropTypes.string,
  flagValue: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string,
    PropTypes.bool,
    PropTypes.symbol
  ]),
  responseDataKeyName: PropTypes.string,
  responseCurrentPageName: PropTypes.string,
  totalRowsKeyName: PropTypes.string,
  listKeyName: PropTypes.string,
  mode: PropTypes.oneOf(['multiple', 'tags']),
  allowClear: PropTypes.bool,
  startSearchIndex: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string
  ]),
  qsStringify: PropTypes.bool,
  selectStyle: PropTypes.object,
  extraSuffix: PropTypes.func,
  selectedOptionList: PropTypes.array,
  onChange: PropTypes.func,
  max: PropTypes.oneOfType([
    PropTypes.number,
    PropTypes.string
  ]),
  lang: PropTypes.oneOf(['zh', 'en', 'es', 'ja']), // 菜单弹出位置
  renderOptions: PropTypes.func,
  responseDataFormat: PropTypes.func,
  requestFail: PropTypes.func
}

export default SearchSelect
